using System;
using System.IO;
using System.Data;
using System.DirectoryServices;
using System.Text.RegularExpressions;
using System.Web.Security;
using System.Security.Principal;
using System.Runtime.InteropServices;

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace EAT.Common
{

    //
    // Helper classes to instantiate users in ADAM and set up required fields.
    //

    

    public class cUserManager
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        static extern int RevertToSelf();

        //
        // INDEX provides indices for the columns of the DataTable.
        //
        enum INDEX
        {
            USER = 0,
            REQUESTER,
            Max
        }

        private WindowsIdentity m_wi;
        private bool m_bReverted;
        private cAdamConnection m_AdamConnection;

        public cAdamConnection Connection
        {
            get
            {
                return m_AdamConnection;
            }
        }

        public cUserManager()
        {
        }

        public cUserManager(String LdapPath)
        {
            try
            {
                this.m_AdamConnection = new cAdamConnection(LdapPath);

                if (this.m_AdamConnection == null)
                {
                    throw new SystemException(Resources.cantCreateADAMConnectionExceptionText);
                }
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        public long DaysUntilPasswordExpires(MembershipUser u)
        {
            TimeSpan pwdMaxTTL;
            DateTime pwdLastSet;
            DateTime pwdExpirationTime;
            TimeSpan TimeUntilExpiration;

            pwdLastSet = DateTime.FromFileTime(u.LastPasswordChangedDate.ToFileTime());
            pwdMaxTTL = PasswordLifetime();
            pwdExpirationTime = pwdLastSet.Add(pwdMaxTTL);

            if (pwdExpirationTime < DateTime.Now)
            {
                return -1;
            }

            TimeUntilExpiration = pwdExpirationTime - DateTime.Now;

            return TimeUntilExpiration.Days;
        }

        public TimeSpan PasswordLifetime()
        {
            DirectorySearcher ds;
            string[] policyAttributes = new string[] {
                                              "maxPwdAge", 
                                              };
            SearchResult result;

            try
            {
                UMRevert();

                ds = new DirectorySearcher(
                             null,
                             "(objectClass=domainDNS)",
                             policyAttributes,
                             SearchScope.Base
                             );
                result = ds.FindOne();
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                UMRestoreImpersonation();
            }


            if (result == null)
            {
                throw new ArgumentException(
                              Resources.domainRootNotDomainDNSExceptionText
                              );
            }
            string val = "maxPwdAge";
            long ticks = 0;

            if (result.Properties.Contains(val))
            {
                // bugbug: math error when no domain PWD policy
                ticks = Math.Abs(
                                 (long)result.Properties[val][0]
                                 );

            }

            TimeSpan ts = TimeSpan.FromTicks(ticks);
            return ts;
        }

        public bool UserMustSetSecretQuestion(
                        MembershipUser U
            )
        {
            if (U.PasswordQuestion == "[change the secret question]")
            {
                return true;
            }
            return false;
        }

        public string GetUserAttribute(
                          MembershipUser U,
                          string Attribute
                          )
        {
            DirectoryEntry de = FindUser(U.UserName);
            string attr = "";
            
            try
            {
                UMRevert();
                attr = (string)de.Properties[Attribute].Value;
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                UMRestoreImpersonation();
            }

            return attr;
        }

        public bool UserMustSetProfileInformation(
                        MembershipUser U
                        )
        {
            //
            // Check if the profile has been touched.
            //
            
            DirectoryEntry de = FindUser(U.UserName);
            string Company;
            string FirstName;
            string LastName;
            string PhoneNumber;

            try
            {
                UMRevert();
                Company = (string)de.Properties["company"].Value;
                FirstName = (string)de.Properties["givenName"].Value;
                LastName = (string)de.Properties["sn"].Value;
                PhoneNumber = (string)de.Properties["telephoneNumber"].Value;
            }
            finally
            {
                UMRestoreImpersonation();
            }
            if (Company == null || Company == "")
            {
                return true;
            }

            if (FirstName == null || FirstName == "")
            {
                return true;
            }

            if (LastName == null || LastName == "")
            {
                return true;
            }

            if (PhoneNumber == null || PhoneNumber == "")
            {
                return true;
            }
            return false;
        }

        public void SetProfileInformation(
                        MembershipUser U,
                        string Company,
                        string FirstName, 
                        string LastName,
                        string PhoneNumber
                        )
        {

            try
            {

                DirectoryEntry de = this.FindUser(U.UserName);

                UMRevert();
                de.Options.PasswordPort = this.Connection.Port();
                de.Properties["company"].Value = HttpUtility.HtmlEncode(Company);
                de.Properties["givenName"].Value = HttpUtility.HtmlEncode(FirstName);
                de.Properties["sn"].Value = HttpUtility.HtmlEncode(LastName);
                de.Properties["telephoneNumber"].Value = HttpUtility.HtmlEncode(PhoneNumber);
                de.CommitChanges();
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                UMRestoreImpersonation();
            }
        }

        public bool PasswordNeverSet(
                        MembershipUser U
                       )
        {
            //
            // Check if the password is generated.
            //
            bool bGenerated = false;
            DirectoryEntry de = FindUser(U.UserName);

            try
            {
                UMRevert();
                
                bGenerated = (bool)de.Properties["eatmuPwdGenerated"].Value;
            }
            catch
            {
            }
            finally
            {
                UMRestoreImpersonation();
            }

            return bGenerated;
        }

        public bool PasswordExpired(
                        MembershipUser U
                        )
        {
            if (DaysUntilPasswordExpires(U) > 0)
                return false;
            else
                return true;
        }

        public bool UserMustSetPassword(
                        MembershipUser U,
                        long PwdWarningDays
                        )
        {
            return (PasswordNeverSet(U) || (DaysUntilPasswordExpires(U) < PwdWarningDays));
        }

        public string ResetPassword(string Username)
        {
            DirectoryEntry de = this.FindUser(Username);
            string Password = Membership.GeneratePassword(9, 2);

            try
            {
                UMRevert();

                de.Options.PasswordPort = this.Connection.Port();
                de.Invoke("SetPassword", new object[] { Password });
                de.Properties["eatmuPwdGenerated"].Value = true;
                de.CommitChanges();
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                UMRestoreImpersonation();
            }
            return Password;
        }

        public void ChangePassword(MembershipUser u, string OldPassword, string NewPassword)
        {
            DirectoryEntry de = this.FindUser(u.UserName);

            try
            {
                UMRevert();

                de.Options.PasswordPort = this.Connection.Port();
                de.Invoke("ChangePassword", new object[] { OldPassword, NewPassword });
                de.Properties["eatmuPwdGenerated"].Value = false;
                de.CommitChanges();
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                UMRestoreImpersonation();
            }
        }

        public bool IsValidUsername(string Username)
        {
            if (Username != null)
            {
                return Regex.IsMatch(Username, @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$");
            }
            else
            {
                return false;
            }
        }

        public DirectoryEntry CreateUser(
                                  String Username,
                                  String Password
                                  )
        {
            
            DirectoryEntry User = null;

            try
            {
                
                UMRevert();

                User = m_AdamConnection.Directory.Children.Add(
                                                              "CN=" + Username,
                                                              "User"
                                                              );

                User.Properties["userPrincipalName"].Value = Username;
                User.CommitChanges();
                User.Options.PasswordPort = this.Connection.Port();
                User.Invoke("SetPassword", new object[] { Password });
                User.CommitChanges();
                User.Properties["msDS-UserAccountDisabled"].Value = false;
                User.CommitChanges();
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                UMRestoreImpersonation();
            }
            return User;
        }

        //
        // The only current user of this function (UserManager.GetUserListFromADAM)
        // reverts itself because of other directory operations. Don't added security
        // context reversion without addressing.
        //
        public SearchResultCollection FindAllUsers()
        {
            DirectorySearcher ds = null;
            SearchResultCollection src = null;

            try
            {
                ds = new DirectorySearcher(
                             this.m_AdamConnection.Directory,
                             "(&(cn=*)(objectClass=user))"
                             );

                src = ds.FindAll();
            }
            catch (Exception e)
            {
                throw e;
            }
            return src;
        }

        public DirectoryEntry FindUser(
                                  String Username
                                  )
        {
            DirectoryEntry de = null;
            DirectorySearcher ds = null;
            SearchResult sr = null;

            try
            {
                UMRevert();

                ds = new DirectorySearcher(
                             this.m_AdamConnection.Directory,
                             "(&(cn=" + Username + ")(objectClass=user))"
                             );
                sr = ds.FindOne();
                de = sr.GetDirectoryEntry();
                
                if (de == null)
                {
                    throw new Exception(Resources.nullDirectoryEntryExceptionText);
                }
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                UMRestoreImpersonation();
            }

            return de;
        }

        public void SetProvisioner(
                        String Username,
                        String Provisioner
                        )
        {
            try
            {

                DirectoryEntry de = FindUser(Username);
                UMRevert();
                de.Properties["eatmuRequester"].Value = Provisioner;
                de.CommitChanges();
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                UMRestoreImpersonation();
            }
        }

        public void SetPwdGenerated(string UserName)
        {

            try
            {
                DirectoryEntry de = FindUser(UserName);
                UMRevert();
                de.Properties["eatmuPwdGenerated"].Value = true;
                de.CommitChanges();
            }
            finally
            {
                UMRestoreImpersonation();
            }

        }

        public bool UserExists(
                        String Username
                        )
        {
            bool bExists = false;
            DirectoryEntry de = null;
            de = this.FindUser(Username);
            if (de != null)
            {
                bExists = true;
            }

            return bExists;
        }

        public void SetDisabledBit(string Username, bool bEnabled)
        {

            try
            {
                DirectoryEntry de = this.FindUser(Username);
                UMRevert();
                de.Properties["msDS-UserAccountDisabled"].Value = bEnabled;
                de.CommitChanges();
            }
            catch (Exception e)
            {
            }
            finally
            {
                UMRestoreImpersonation();
            }
        }

        //
        // ToggleUserDisabledState
        // Arguments:
        //      Username - the user to modify
        // Description:
        //      Toggles the state of the user based on msDS-UserAccountDisabled.
        //
        public void ToggleUserDisabledState(string Username)
        {
            try
            {

                DirectoryEntry de = FindUser(Username);
                UMRevert();

                if (de.Properties["msDS-UserAccountDisabled"].Value.ToString().ToLower() == "false")
                {
                    SetDisabledBit(Username, true);
                }
                else
                {
                    SetDisabledBit(Username, false);
                }
            }
            catch (Exception e)
            {
            }
            finally
            {
                UMRestoreImpersonation();
            }
        }


        public void DeleteUser(
                        String Username
                        )
        {

            try
            {
                DirectoryEntry User = this.FindUser(Username);

                UMRevert();
                m_AdamConnection.Directory.Children.Remove(User);
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                UMRestoreImpersonation();
            }
        }

        //
        // GetPendingUsers
        // Arguments:
        //      dt - the datatable to populate
        // Description:
        //      This routine fills dt with the disabled users found in the DS
        //      specified.
        //
        public void GetPendingUsers(
                        DataTable dt
                        )
        {

            try
            {

                UMRevert();

                string Filter = "(&(objectClass=user)(msDS-UserAccountDisabled=TRUE))";
                
                DirectorySearcher Search = new DirectorySearcher(
                                                   this.m_AdamConnection.Directory,
                                                   Filter
                                                   );

                SearchResultCollection SearchResults = null;
                SearchResults = Search.FindAll();

                DataRow dr;

                foreach (SearchResult res in SearchResults)
                {
                    dr = dt.NewRow();
                    dr[(int)INDEX.USER] = res.GetDirectoryEntry().Properties["name"][0];
                    try
                    {
                        dr[(int)INDEX.REQUESTER] = res.GetDirectoryEntry().Properties["eatmuRequester"][0];
                    }
                    catch
                    {
                        dr[(int)INDEX.REQUESTER] = Resources.noRequesterText;
                    }
                    dt.Rows.Add(dr);
                }
            }
            catch (COMException ex)
            {
                if (-2147016646 == ex.ErrorCode)
                {
                    return;
                }
                else
                {
                    throw ex;
                }
            }
            finally
            {
                UMRestoreImpersonation();
            }

        }
        private void UMRevert()
        {
            m_wi = null;
            m_bReverted = false;
            // In general, we do things related to ADAM account management as the process
            // (IIS App Pool) identity. Revert to service account.
            m_wi = WindowsIdentity.GetCurrent();
            RevertToSelf();
            m_bReverted = true;
            WindowsIdentity.GetCurrent().Impersonate();

        }
        private void UMRestoreImpersonation()
        {
            if (m_bReverted && m_wi != null)
            {
                m_wi.Impersonate();
            }
        }

    }

    public class cAdamConnection
    {
        private DirectoryEntry m_DE = null;
        private String m_ConnectionString = null;

        public cAdamConnection()
        {
            m_DE = new DirectoryEntry();
        }

        public cAdamConnection(
                   String ConnectionString
                   )
        {
            this.m_ConnectionString = ConnectionString;

            m_DE = new DirectoryEntry(
                           this.m_ConnectionString,
                           null,
                           null,
                           AuthenticationTypes.SecureSocketsLayer | AuthenticationTypes.Secure
                           );
        }
        
        public String ConnectionString
        {
            get
            {
                return m_ConnectionString;
            }
        }

        public DirectoryEntry Directory
        {
            get
            {
                return m_DE;
            }
        }

        public int Port()
        {
            string conn = m_ConnectionString;
            int i;
            int j;
            string port;

            // 
            // find the second ':'.  This indicates the port number.
            //
            i = conn.IndexOf(':', 0);
            i = conn.IndexOf(':', i + 1);
            j = conn.IndexOf('/', i);

            //
            // The port starts at i + 1 and goes until j.
            //
            port = conn.Substring(i + 1, j - i - 1);
            return int.Parse(port);
        }

        public void Connect(String ConnectionString)
        {
            m_DE.Path = ConnectionString;
            m_ConnectionString = m_DE.Path;
        }
    }
}